﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using FileRenamer.Events;
using Helper;
using WpfControls;

namespace FileRenamer
{
	public delegate void ErrorAddedHandler(object sender, MessageArgs e);
	public delegate void ProgressChangedHandler(object sender, MessageArgs e);
	public delegate void WorkOperationCompleteHandler(object sender, GetFilesCompleteArgs e);

	public class FileRenamerSystem
	{
		private ListOfFiles listOfFiles = new ListOfFiles();
		public List<String> tempExclude = new List<string>();

		#region events
		public event ErrorAddedHandler ErrorAdded;
		public event WorkOperationCompleteHandler WorkOperationComplete;
		public event ProgressChangedHandler ProgressChanged;
		#endregion

		#region Private Fields
		private BackgroundWorker threadPreview, threadRename, threadUndo;
		private string commandText = "";
		private List<string> errorList;
		#endregion

		public FileRenamerSystem()
		{
			errorList = new List<string>();

			threadPreview = new BackgroundWorker();
			threadPreview.WorkerReportsProgress = true;
			threadPreview.DoWork += threadPreviewWork;
			threadPreview.RunWorkerCompleted += workOperationComplete;
			threadPreview.ProgressChanged += new ProgressChangedEventHandler(Woker_ProgressChanged);

			threadRename = new BackgroundWorker();
			threadRename.WorkerReportsProgress = true;
			threadRename.DoWork += threadReanameWork;
			threadRename.RunWorkerCompleted += workOperationComplete;
			threadRename.ProgressChanged += new ProgressChangedEventHandler(Woker_ProgressChanged);

			threadUndo = new BackgroundWorker();
			threadUndo.WorkerReportsProgress = true;
			threadUndo.DoWork += threadUndoWork;
			threadUndo.RunWorkerCompleted += workOperationComplete;
			threadUndo.ProgressChanged += new ProgressChangedEventHandler(Woker_ProgressChanged);
		}

		#region Invoke Events
		protected virtual void OnErrorAdded(MessageArgs e)
		{
			if (ErrorAdded != null) ErrorAdded(this, e);
		}

		protected virtual void OnGetFilesCompleted(GetFilesCompleteArgs e)
		{
			if (WorkOperationComplete != null) WorkOperationComplete(this, e);
		}

		protected virtual void OnProgressChanged(MessageArgs e)
		{
			if (ProgressChanged != null) ProgressChanged(this, e);
		}
		#endregion

		#region Public Methods

		#region GetFiles
		public void GetFiles(ListOfFiles.SessionData sessionData, String command)
		{
			listOfFiles.Session = sessionData;
			if (Directory.Exists(sessionData.RootPath))
			{
				listOfFiles.Clear();

				errorList.Clear();

				commandText = command;

				Globals.Settings.FolderPath = sessionData.RootPath;
				Globals.Settings.Recursive = sessionData.Recursive;
				Globals.Settings.Save();

				threadPreview.RunWorkerAsync();
			}
			else
			{
				addToErrorList("Directory does not exist: " + listOfFiles.Session.RootPath);
			}
		}
		#endregion

		#region Rename Files
		/// <summary>
		/// Renames the files using the current listOfFiles
		/// </summary>
		public void RenameFiles()
		{
			threadRename.RunWorkerAsync();
		}
		#endregion

		#region UndoRename
		/// <summary>
		/// Undoes whatever operation was done last. Do not call this if you haven't done an operation.
		/// </summary>
		public void UndoRename()
		{
			threadUndo.RunWorkerAsync();
		}
		#endregion

		#region UpdateNewFileName
		/// <summary>
		/// Searches through the list of files for oldFile. Updates its new filename to newFile.
		/// Used for manual name updates.
		/// </summary>
		/// <param name="oldFile">The file to search for</param>
		/// <param name="newFile">The new name that oldFile should be renamed to</param>
		public void UpdateNewFileName(String oldFile, String newFile)
		{
			int i = listOfFiles.IndexOfOldName(oldFile);
			listOfFiles[i].NewFileName = new FileInfo(newFile);
		}
		#endregion

		#region ClearExcludeList
		/// <summary>
		/// Clears the list of files to be exlcuded in the batch.
		/// </summary>
		public void ClearExcludeList()
		{
			tempExclude.Clear();
		}
		#endregion

		#region RemoveFromList
		/// <summary>
		/// Adds fullName to the list of files to eclude.
		/// If the current operation is to Increment, then run the preview thread again.
		/// </summary>
		/// <param name="fullName"></param>
		/// <returns></returns>
		public ListOfFiles RemoveFromList(String fullName)
		{
			try
			{
				tempExclude.Add(fullName);

				if (listOfFiles.Session.Mode != "tabIncrement")
				{
					listOfFiles.RemoveFile(fullName);
				}
				else
				{
					threadPreview.RunWorkerAsync();
				}

			}
			catch (Exception e)
			{
				addToErrorList("Error removing file: " + fullName + "; Message: " + e.Message);
			}
			return listOfFiles;
		}
		#endregion

		#endregion

		#region threadPreviewWork
		/// <summary>
		/// Gets a list of all files based on the listOfFiles.Session data, and adds them to listOfFiles
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void threadPreviewWork(object sender, DoWorkEventArgs e)
		{
			List<FileInfo> files = HFile.GetAllFiles(listOfFiles.Session.RootPath, listOfFiles.Session.Recursive, listOfFiles.Session.OnlyExt,
				listOfFiles.Session.ExcludeExt, tempExclude.ToArray());
			foreach (FileInfo f in files) listOfFiles.Add(f);
			listOfFiles.Sort();

			setNewNames();
		}
		#endregion

		#region threadRenameWork
		private void threadReanameWork(object sender, DoWorkEventArgs e)
		{
			int i = 0;
			foreach (FileToRename f in listOfFiles)
			{
				try
				{
					if (File.Exists(f.FileName.FullName) && f.NewFileName != null)
					{
						moveFile(f.FileName.FullName, f.NewFileName.FullName, i);
					}
					threadRename.ReportProgress((int)((float)i / (float)listOfFiles.Count * 100f), f.FileName.Name);

					i++;
				}
				catch (Exception ex)
				{
					addToErrorList(f.ShortFileName, ex.Message);
				}
			}
		}
		#endregion

		#region threadUndoWork
		private void threadUndoWork(object sender, DoWorkEventArgs e)
		{
			int i = 0;
			foreach (FileToRename f in listOfFiles)
			{
				if (File.Exists(f.NewFileName.FullName))
				{
					moveFile(f.NewFileName.FullName, f.FileName.FullName, i);
				}
				i++;
			}
		}
		#endregion

		#region Worker_ProgressChanged
		/// <summary>
		/// Updates the progress. Called by all workers.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void Woker_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			String message = "";
			try
			{
				message = (string)e.UserState;
			}
			catch
			{
				message = "oops, error occurred";
			}
			finally
			{
				OnProgressChanged(new MessageArgs(message));
			}
		}
		#endregion

		#region workOperationComplete
		private void workOperationComplete(object sender, RunWorkerCompletedEventArgs e)
		{
			OnGetFilesCompleted(new GetFilesCompleteArgs(listOfFiles));
		}
		#endregion

		#region setNewNames
		/// <summary>
		/// Sets the new names for all of the files in listOfFiles
		/// </summary>
		private void setNewNames()
		{
			int i = 0;
			Expression exp = new Expression();
			exp.Special = new SpecialChars('\\', '{', '}');
			foreach (FileToRename f in listOfFiles)
			{
				if (i % 10 == 0)
					threadPreview.ReportProgress((int)((float)i / (float)listOfFiles.Count * 100f), "Parsing: " + f.ShortFileName);
				string noExt = f.FileName.Name.Substring(0, f.FileName.Name.Length - f.FileName.Extension.Length);
				//old code: f.SetNewName(listOfFiles.session, i);
				exp.Input = ParseExtraCommands(commandText, noExt, i);
				string newFileName = exp.Parse();		//magical/main logic part
				if (String.IsNullOrEmpty(newFileName))
					newFileName = HFile.RemoveExtension(f.ShortFileName);

				f.NewFileName = new FileInfo(HFile.CombinePath(f.FileName.DirectoryName, newFileName + f.FileName.Extension));
				i++;
			}
		}
		#endregion

		#region moveFile - Validate new filename and rename
		private bool moveFile(String source, String dest, int pos)
		{
			try
			{
				FileInfo info = new FileInfo(source);
				dest = GetFilePath(dest) + HFile.RemoveIllegalChars(GetFileName(dest, true));
				if (File.Exists(dest) && source.ToLower() != dest.ToLower())
				{
					while (source.ToLower() == dest.ToLower())
					{
						dest = InputBox.Show("File Already Exists", "Please choose a different name or click Skip", GetFileName(dest, true), "Ok", "Skip", 
							new Point(Globals.WindowLocation.X, Globals.WindowLocation.Y), new Point(500, 500));

						if (dest == null) return false;
					}
				}
				else if (!File.Exists(dest) || source != dest)
				{
					info.MoveTo(dest);
					listOfFiles[pos].BrushStatus = Brushes.Green;
				}
				return true;
			}
			catch (Exception e)
			{
				listOfFiles[pos].BrushStatus = Brushes.Red;
				addToErrorList(source, e.Message);
				return false;
			}
		}
		#endregion

		#region ParseExtraCommands
		public static string ParseExtraCommands(String command, String fileName, int count)
		{
			try
			{
				String newCom = command;
				newCom = newCom.Replace("[sp].length", fileName.Length.ToString());
				newCom = newCom.Replace("[filename]", fileName);
				if (newCom.Contains("[count."))
				{
					int startIndex = newCom.IndexOf("[count.") + 7;
					string sNumber = newCom.Substring(startIndex, newCom.IndexOf("]", startIndex + 1) - startIndex);
					string digits = "";
					int start = int.Parse(sNumber);

					for (int i = 0; i < sNumber.Length; i++) digits += "0";
					string front = newCom.Substring(0, startIndex - 7);
					string formattedNum = String.Format("{0:" + digits + "}", start + count);
					string end = newCom.Substring(startIndex + sNumber.Length + 1);
					newCom = front + formattedNum + end;
				}

				return newCom;
			}
			catch
			{
				return command;
			}
		}
		#endregion

		#region AddToErrorList
		private void addToErrorList(String message)
		{
			String error = DateTime.Today.ToLocalTime() + " - " + message;
			errorList.Add(error);

			OnErrorAdded(new MessageArgs(error));
		}
		private void addToErrorList(String filename, String errorMessage)
		{
			String error = DateTime.Today.ToLocalTime() + " - Error adding: " + filename + "; Exception: " + errorMessage;
			errorList.Add(error);

			OnErrorAdded(new MessageArgs(error));
		}

		#endregion

		#region Parse FileName/FilePath
		public static String GetFileName(String filePath, Boolean includeExtension)
		{
			if (includeExtension)
				return filePath.Substring(filePath.LastIndexOf('\\') + 1);
			else
				return filePath.Substring(filePath.LastIndexOf('\\') + 1, filePath.LastIndexOf('.') - filePath.LastIndexOf('\\') - 1);
		}

		public static String GetFilePath(String filePath)
		{
			return filePath.Substring(0, filePath.LastIndexOf("\\") + 1);
		}
		#endregion
	}
}
